<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>知识图谱</title>
    <meta name="description" content="" />
    <meta name="keywords" content="" />
    <meta name="author" content="" />
    <link rel="shortcut icon" href="">
    <script src="http://cdn.bootcss.com/jquery/2.1.4/jquery.min.js"></script>
    <link href="http://cdn.bootcss.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">
    <script src="http://cdn.bootcss.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
</head>
<style type="text/css">
body {
	background-color: #272b30;
	padding: 30px 40px;
	text-align: center;
    font-family: OpenSans-Light, PingFang SC, Hiragino Sans GB, Microsoft Yahei, Microsoft Jhenghei, sans-serif;
}

#indicator {
	position: absolute;
	left: 60px;
	bottom: 120px;
    text-align: left;
    color: #f2f2f2;
    font-size: 12px;
}

#indicator>div {
    margin-bottom: 4px;
}

#indicator span {
    display: inline-block;
    width: 30px;
    height: 14px;
    position: relative;
    top: 2px;
    margin-right: 8px;
}

.links line {
	stroke: rgb(240, 240, 240);
	stroke-opactity: 0.2;
}

.links line.inactive {
    /*display: none !important;*/
    stroke-opacity: 0;
}

.nodes circle {
	stroke: #fff;
	stroke-width: 1.5px;
}

.nodes circle:hover {
    cursor: pointer;
}

.nodes circle.inactive {
	display: none !important;
}

.texts text {
    display: none;
}

.texts text:hover {
    cursor: pointer;
}

.texts text.inactive {
    display: none !important;
}



#mode {
    position: absolute;
    top: 160px;
    left: 60px;
}

#mode span {
    display: inline-block;
    border: 1px solid #fff;
    color: #fff;
    padding: 6px 10px;
    border-radius: 4px;
    font-size: 14px;
    transition: color, background-color .3s;
    -o-transition: color, background-color .3s;
    -ms-transition: color, background-color .3s;
    -moz-transition: color, background-color .3s;
    -webkit-transition: color, background-color .3s;
}

#mode span.active, #mode span:hover {
    background-color: #fff;
    color: #333;
    cursor: pointer;
}


#info {
    position: absolute;
    bottom: 40px;
    right: 30px;
    text-align: right;
    width: 270px;
}


#info p {
    color: #fff;
    font-size: 12px;
    margin-top: 0;
    margin-bottom: 5px;
}

#info p span {
    color: #888;
    margin-right: 10px;
}

#info h4 {
    color: #fff;
}


#search input {
    position: absolute;
    top: 220px;
    left: 60px;
    color: #fff;
    border: none;
    outline: none;
    box-shadow: none;
    width: 200px;
    background-color: #666;
}

#svg2 g.row:hover {
    stroke-width: 1px;
    stroke: #fff;
}

</style>
<body>

	<!-- 绘制标题样式 -->
	<h1 style="color:#fff;font-size:32px;margin-bottom:0px;text-align:center;margin-left:40px;">Star Wars</h1>

	<!-- 第一个布局 绘制知识图谱主图 -->
	<div style="text-align: center; position:relative;">
		<svg width="800" height="560" style="margin-right:80px;margin-bottom:-40px;" id="svg1">
    	</svg>

    	<!-- 绘制图例 -->
		<div id="indicator">
	    </div>

	    <!-- 绘制模式选择 -->
	    <div id="mode">
	    	<span class="active" style="border-top-right-radius:0;border-bottom-right-radius:0;">节点</span>
            <span style="border-top-left-radius:0;border-bottom-left-radius:0;position:relative;left:-5px;">文字</span>
	    </div>

	    <!-- 绘制搜索框 -->
	    <div id="search">
	    	<input type="text" class="form-control">
	    </div>

	    <!-- 绘制右边显示结果 -->
	    <div id="info">
	    	<h4></h4>
	    </div>
	</div>

	<!-- 第二个布局 下部分时间点 文本居中 相对定位-->
	<div style="text-align: center; position:relative;">
		<svg width="960" height="240" style="margin-right:60px;margin-bottom:-40px;" id="svg2">
			<g></g>
    	</svg>
	</div>

</body>

<!-- 增加D3元素库 -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<!-- 补充JS代码 -->
<script type="text/javascript">
	$(document).ready(function() {
		//定义svg变量将布局svg1选出来
		var svg = d3.select("#svg1"),
			width = svg.attr("width"),
			height = svg.attr("height");

		//定义name变量制作图标
		var names = ['Films', 'Characters', 'Planets', 'Starships', 'Vehicles', 'Species'];
		var colors = ['#6ca46c', '#4e88af', '#ca635f', '#d2907c', '#d6744d', '#ded295'];

		//背景颜色设置 补充CSS样式设置字体布局
		for (var i=0; i < names.length; i++) {
			$('#indicator').append("<div><span style='background-color:" + colors[i] + "'></span>" + names[i] + "</div>");
		}

		//利用d3.forceSimulation()定义关系图 包括设置边link、排斥电荷charge、关系图中心点
		var simulation = d3.forceSimulation()
	        .force("link", d3.forceLink().id(function(d) {
	            return d.id;
	        }))
	        .force("charge", d3.forceManyBody())
	        .force("center", d3.forceCenter(width / 2, height / 2));


	    //存储关系图的数据
	    var graph;

	    //定义d3.json请求python处理好的节点及边 请求成功返回数据，否则报错
	    d3.json("starwar_alldata.json", function(error, data) {
	    	if(error) throw error;
	    	graph = data;
	    	console.log(graph);

	    	//D3映射数据至HTML中
	    	//g用于绘制所有边,selectALL选中所有的line,并绑定数据data(graph.links),enter().append("line")添加元素
	    	//数据驱动文档,设置边的粗细
	    	//前面定义var svg = d3.select("#svg1")
	    	var link = svg.append("g").attr("class","links").selectAll("line").data(graph.links)
	    	.enter().append("line").attr("stroke-width", function(d) {
	    		//return Math.sqrt(d.value);
	    		return 1; //所有线宽度均为1
	    	});

	    	//添加所有的点
	    	//selectAll("circle")选中所有的圆并绑定数据,圆的直径为d.size
	    	//再定义圆的填充色,同样数据驱动样式,圆没有描边,圆的名字为d.id
	    	//call()函数：拖动函数,当拖动开始绑定dragstarted函数，拖动进行和拖动结束也绑定函数
	    	var node = svg.append("g").attr("class", "nodes").selectAll("circle").data(graph.nodes)
	    	.enter().append("circle").attr("r", function(d) {
	    		return d.size;
	    	}).attr("fill", function(d) {
	    		return colors[d.group];
	    	}).attr("stroke", "none").attr("name", function(d) {
	    		return d.id;
	    	}).call(d3.drag()
	    		.on("start", dragstarted)
	    		.on("drag", dragged)
	    		.on("end", dragended)
	    	);

	    	//显示所有的文本
	    	//设置大小、填充颜色、名字、text()设置文本
	    	//attr("text-anchor", "middle")文本居中
	    	var text = svg.append("g").attr("class", "texts").selectAll("text").data(graph.nodes)
	    	.enter().append("text").attr("font-size", function(d) {
                return d.size;
            }).attr("fill", function(d) {
                return colors[d.group];
            }).attr('name', function(d) {
                return d.id;
            }).text(function(d) {
                return d.id;
            }).attr('text-anchor', 'middle').call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended)
            );

	    	//圆增加title
	    	node.append("title").text(function(d) {
	    		return d.id;
	    	})

	    	//simulation中ticked数据初始化，并生成图形
	    	simulation
	            .nodes(graph.nodes)
	            .on("tick", ticked);

	        simulation.force("link")
	            .links(graph.links);

	        //ticked()函数确定link线的起始点x、y坐标 node确定中心点 文本通过translate平移变化
	        function ticked() {
	            link
	                .attr("x1", function(d) {
	                    return d.source.x;
	                })
	                .attr("y1", function(d) {
	                    return d.source.y;
	                })
	                .attr("x2", function(d) {
	                    return d.target.x;
	                })
	                .attr("y2", function(d) {
	                    return d.target.y;
	                });

	            node
	                .attr("cx", function(d) {
	                    return d.x;
	                })
	                .attr("cy", function(d) {
	                    return d.y;
	                });

	            text.
	            attr('transform', function(d) {
	                return 'translate(' + d.x + ',' + (d.y + d.size / 2) + ')';
	            });
	        }
	    });


	    // Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension
	    // 本地json数据需要放置服务器中请求 XAMPP


	    //该变量保证拖动鼠标时，不会影响图形变换，默认为false未选中鼠标
	    var dragging = false;

	    //开始拖动并更新相应的点
	    function dragstarted(d) {
	        if (!d3.event.active) simulation.alphaTarget(0.3).restart();
	        d.fx = d.x;
	        d.fy = d.y;
	        dragging = true;
	    }

	    //拖动进行中
	    function dragged(d) {
	        d.fx = d3.event.x;
	        d.fy = d3.event.y;
	    }

	    //拖动结束
	    function dragended(d) {
	        if (!d3.event.active) simulation.alphaTarget(0);
	        d.fx = null;
	        d.fy = null;
	        dragging = false;
	    }


	    //span点击事件
	    $('#mode span').click(function(event) {
	    	//span都设置为不激活状态
	        $('#mode span').removeClass('active');

	        //点击的span被激活
	        $(this).addClass('active');

	        //text隐藏 nodes显示
	        if ($(this).text() == '节点') {
	            $('.texts text').hide();
	            $('.nodes circle').show();
	        } else {
	            $('.texts text').show();
	            $('.nodes circle').hide();
	        }
    	});

	    //为svg1父元素下的.nodes circle元素绑定鼠标进入事件
    	$('#svg1').on('mouseenter', '.nodes circle', function(event) {
    		//通过变量dragging保证拖动鼠标时，其状态不受影响，从而改变图形
    		//鼠标没有拖动才能处理事件
    		if(!dragging) {
    			//获取被选中元素的名字
	    		var name = $(this).attr("name");

	    		//设置#info h4样式的颜色为该节点的颜色，文本为该节点name
	    		//$(this).attr('fill')表示当前悬浮圆的填充色
	    		$('#info h4').css('color', $(this).attr('fill')).text(name);

	    		//每次点击添加属性前把上次显示的信息去除，否则会不断叠加
	    		$('#info p').remove();

	    		//打印悬浮的节点信息
	    		//console.log(info[name]);

	    		//遍历所有的
	    		for (var key in info[name]) {
	    			//类型复杂的不进行显示
	    			if (typeof(info[name][key]) == 'object') {
                    	continue;
                	}
                	//比较复杂的超链接字段不显示
	                if (key == 'url' || key == 'title' || key == 'name' ||
	                	key == 'edited' || key == 'created' || key == 'homeworld') {
	                    continue;
	                }
	                //显示值及其字段名字
	                $('#info').append('<p><span>' + key + '</span>' + info[name][key] + '</p>');
	    		}


	    		//选择#svg1 .nodes中所有的circle，再增加个class
	    		d3.select('#svg1 .nodes').selectAll('circle').attr('class', function(d) {
	    			//数据的id是否等于name,返回空
	    			if(d.id==name) {
	    				return '';
	    			}
	    			//当前节点返回空，否则其他节点循环判断是否被隐藏起来(CSS设置隐藏)
	    			else {
	    				//links链接的起始节点进行判断,如果其id等于name则显示这类节点
	    				//注意: graph=data
	    				for (var i = 0; i < graph.links.length; i++) {
	    					//如果links的起点等于name，并且终点等于正在处理的则显示
		                    if (graph.links[i]['source'].id == name && graph.links[i]['target'].id == d.id) {
		                        return '';
		                    }
		                    if (graph.links[i]['target'].id == name && graph.links[i]['source'].id == d.id) {
		                        return '';
		                    }
		                }

	    				return "inactive"; //前面CSS定义 .nodes circle.inactive
	    			}
	    		});

	    		//处理相邻的边line是否隐藏 注意 ||
	    		d3.select("#svg1 .links").selectAll('line').attr('class', function(d) {
	                if (d.source.id == name || d.target.id == name) {
	                    return '';
	                } else {
	                    return 'inactive';
	                }
	            });
    		}
   		});

    	//鼠标移开还原原图，显示所有隐藏的点及边
    	$('#svg1').on('mouseleave', '.nodes circle', function(event) {
    		//如果dragging为false才处理事件
    		if(!dragging) {
    			d3.select('#svg1 .nodes').selectAll('circle').attr('class', '');
           		d3.select('#svg1 .links').selectAll('line').attr('class', '');
    		}
	    });


    	//鼠标进入文本显示相邻节点及边
    	$('#svg1').on('mouseenter', '.texts text', function(event) {
	        if (!dragging) {
	            var name = $(this).attr('name');

	            //同样的代码从选中圆中赋值过来
	            $('#info h4').css('color', $(this).attr('fill')).text(name);
	            $('#info p').remove();
	            for (var key in info[name]) {
	                if (typeof(info[name][key]) == 'object') {
	                    continue;
	                }
	                if (key == 'url' || key == 'title' || key == 'name' || key == 'edited' || key == 'created' || key == 'homeworld') {
	                    continue;
	                }
	                $('#info').append('<p><span>' + key + '</span>' + info[name][key] + '</p>');
	            }

	            d3.select('#svg1 .texts').selectAll('text').attr('class', function(d) {
	                if (d.id == name) {
	                    return '';
	                }

	                for (var i = 0; i < graph.links.length; i++) {
	                    if (graph.links[i]['source'].id == name && graph.links[i]['target'].id == d.id) {
	                        return '';
	                    }
	                    if (graph.links[i]['target'].id == name && graph.links[i]['source'].id == d.id) {
	                        return '';
	                    }
	                }
	                return 'inactive';
	            });
	            d3.select("#svg1 .links").selectAll('line').attr('class', function(d) {
	                if (d.source.id == name || d.target.id == name) {
	                    return '';
	                } else {
	                    return 'inactive';
	                }
	            });
	        }
	    });

    	//鼠标移除文本还原相应节点及边
	    $('#svg1').on('mouseleave', '.texts text', function(event) {
	        if (!dragging) {
	            d3.select('#svg1 .texts').selectAll('text').attr('class', '');
	            d3.select('#svg1 .links').selectAll('line').attr('class', '');
	        }
	    });


	    //搜索框中输入内容则响应该事件
	    //keyup按键敲击响应event
	    $('#search input').keyup(function(event) {
	    	//如果Input值是空的显示所有的圆和线(没有进行筛选)
	        if ($(this).val() == '') {
	            d3.select('#svg1 .texts').selectAll('text').attr('class', '');
	            d3.select('#svg1 .nodes').selectAll('circle').attr('class', '');
	            d3.select('#svg1 .links').selectAll('line').attr('class', '');
	        }
	        //否则判断判断三个元素是否等于name值，等于则显示该值
	        else {
	            var name = $(this).val();
	            //搜索所有的节点
	            d3.select('#svg1 .nodes').selectAll('circle').attr('class', function(d) {
	            	//输入节点id的小写等于name则显示，否则隐藏
	                if (d.id.toLowerCase().indexOf(name.toLowerCase()) >= 0) {
	                    return '';
	                } else {
	                	//优化：与该搜索节点相关联的节点均显示
	                	//links链接的起始节点进行判断,如果其id等于name则显示这类节点
	    				//注意: graph=data
	    				for (var i = 0; i < graph.links.length; i++) {
	    					//如果links的起点等于name，并且终点等于正在处理的则显示
		                    if ((graph.links[i]['source'].id.toLowerCase().indexOf(name.toLowerCase()) >= 0) &&
		                    	(graph.links[i]['target'].id == d.id)) {
		                        return '';
		                    }
		                    //如果links的终点等于name，并且起点等于正在处理的则显示
		                    if ((graph.links[i]['target'].id.toLowerCase().indexOf(name.toLowerCase()) >= 0) &&
		                    	(graph.links[i]['source'].id == d.id)) {
		                        return '';
		                    }
		                }

		                return 'inactive'; //隐藏其他节点
	                }

	            });


	            //搜索texts
	            d3.select('#svg1 .texts').selectAll('text').attr('class', function(d) {
	                if (d.id.toLowerCase().indexOf(name.toLowerCase()) >= 0) {
	                    return '';
	                } else {
	                	//优化：与该搜索节点相关联的节点均显示
	                	//links链接的起始节点进行判断,如果其id等于name则显示这类节点
	    				//注意: graph=data
	    				for (var i = 0; i < graph.links.length; i++) {
	    					//如果links的起点等于name，并且终点等于正在处理的则显示
		                    if ((graph.links[i]['source'].id.toLowerCase().indexOf(name.toLowerCase()) >= 0) &&
		                    	(graph.links[i]['target'].id == d.id)) {
		                        return '';
		                    }
		                    //如果links的终点等于name，并且起点等于正在处理的则显示
		                    if ((graph.links[i]['target'].id.toLowerCase().indexOf(name.toLowerCase()) >= 0) &&
		                    	(graph.links[i]['source'].id == d.id)) {
		                        return '';
		                    }
		                }

	                    return 'inactive'; //隐藏其他节点
	                }
	            });


	            //搜索links 所有与搜索name相关联的边均显示
	            //显示相的邻边 注意 ||
		        //name=$(this).val()：名字为键盘输入的内容
	            d3.select("#svg1 .links").selectAll('line').attr('class', function(d) {
	            	if ((d.source.id.toLowerCase().indexOf(name.toLowerCase()) >= 0) ||
	            		(d.target.id.toLowerCase().indexOf(name.toLowerCase()) >= 0)
	            		) {
	                    return '';
	                } else {
	                    return 'inactive'; //隐藏
	                }
	            });
	        }
	    }); //end input


	    //加载Python获取的Json信息：六类实体详细属性信息
	    var info;

	    //d3.json获取数据
	    d3.json("all_data.json", function(error, data) {
	    	if(error) throw error;
	    	info = data;
	    });

		//请求时间轴数据
		d3.json("all_timeline.json", function(error, data) {
			if(error) throw error;

			//前面HTML设置第二个布局 高度为240px 宽度960px，减去40px间距
			var height2 = 240 - 40;
			var width2 = 960;


			/* 第一步 设置最左边的电影名称*/

			//选择#svg2 g布局绑定数据
			//selectAll('text.film'):选择所有text并且存在class属性为film的
			//加载的data数据包括：films和data两个值
			d3.select('#svg2 g').selectAll('text.film').data(data['films']).enter().append('text').text(function(d) {
				//获取文本 d[0]文本 d[1]上映时间
			    return d[0];
			}).attr('transform', function(d, i) {
				//添加变化transform，其中translate表示平移
				//x方向平移固定值150，y方向：平移初始值40、i表示第几条
				//height2/data['films'].length)表示高度除以7部电影，即每部显影平均下来的高度
			    return 'translate(150,' + (40 + (i + 0.5) * height2 / data['films'].length) + ')';
			}).attr('fill', '#fff').attr('font-size', 12)
			.attr('text-anchor', 'end').attr('class', 'film'); //填充色、字体大小、文本右对齐、class属性


			/* 第二步 设置X轴顶部显示实体名称*/

			//获取#svg2 g中的text.title
			//data(data['data'])获取具体的数据
			d3.select('#svg2 g').selectAll('text.title').data(data['data']).enter().append('text').text(function(d) {
			    return d['name']; //表示实体的名称
			}).attr('name',function(d) {
			    return d['name']; //取个名字name属性
			}).attr('transform', function(d, i) {
				//横向平移 y轴方向均为25
			    return 'translate(' + (165 + i * (width2 - 165) / data['data'].length) + ',25)';
			}).attr('fill', '#fff').attr('font-size', 12).attr('text-anchor', 'middle')
			.attr('class', 'title').attr('fill-opacity', 0);
			//透明度设置为0 .attr('fill-opacity', 0)


			/* 第三步 绘制方框矩阵 */

		    //矩形颜色 没有电影五种颜色
		    var colors2 = ['#4e88af', '#ca635f', '#d2907c', '#d6744d', '#ded295'];

		    for (var i = 0; i < data['data'].length; i++) {

		    	//获取第i个值
		        var tmp = data['data'][i];

		        //对每个节点#svg2 g再增加一个g，并定义一个id值
		        //选中第二个g中所有的矩形rect，再绑定数据tmp['vector']
		        //设置宽度、长度、填充色
		        d3.select('#svg2 g').append('g').attr('id', tmp['name']).attr('class', 'row')
		            .selectAll('rect').data(tmp['vector']).enter().append('rect')
		            .attr('width', Math.floor((width2 - 165) / data['data'].length))
		            .attr('height', (height2 / data['films'].length - 1))
		            .attr('fill', function(d, j) {
		                return colors2[tmp['group']];
		            }).attr('transform', function(d, j) {
		            	//矩形平移值 X轴表示实体i的宽度 Y轴表示高度
		                return 'translate(' + (165 + i * (width2 - 165) / data['data'].length) + ','
		                	+ (40 + j * height2 / data['films'].length) + ')';
		            }).attr('fill-opacity', function(d) {
		            	//设置透明度
		                if (d == 1) {
		                    return 1; //显示
		                }
		                else if (d == 0) {
		                    return 0; //隐藏
		                }
		            });
		    } //end for


			//打印节点
			console.log(data);
		});

		/* 第四步 选中节点显示对应的实体名称 */

	    //鼠标选中g.row则显示该名称
	    $('#svg2').on('mouseenter', 'g.row', function(event) {
	        event.preventDefault();
	        //#svg2 text.title透明度设置为0
	        $('#svg2 text.title').attr('fill-opacity',0);
	        console.log($(this).attr('id'));

	        //设置名称
	        $('#svg2 text.title[name="' + $(this).attr('id') + '"]').attr('fill-opacity', 1);
	    });

	    //鼠标移开名字
	    $('#svg2').on('mouseleave', 'g.row', function(event) {
	        event.preventDefault();
	        $('#svg2 text.title').attr('fill-opacity',0);
	    });

	});
</script>
</html>
